// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_
#define CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_

#include <stdint.h>

#include <map>
#include <memory>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "content/common/appcache_interfaces.h"
#include "content/common/content_export.h"
#include "content/public/browser/appcache_service.h"
#include "net/base/completion_callback.h"
#include "net/base/net_errors.h"
#include "storage/browser/quota/quota_manager_proxy.h"

namespace base {
class FilePath;
class SingleThreadTaskRunner;
} // namespace base

namespace net {
class URLRequestContext;
} // namespace net

namespace storage {
class SpecialStoragePolicy;
} // namespace storage

namespace content {
FORWARD_DECLARE_TEST(AppCacheServiceImplTest, ScheduleReinitialize);
class AppCacheBackendImpl;
class AppCacheExecutableHandlerFactory;
class AppCacheQuotaClient;
class AppCachePolicy;
class AppCacheServiceImplTest;
class AppCacheStorageImplTest;
class AppCacheStorage;

// Refcounted container to manage the lifetime of the old storage instance
// during Reinitialization.
class CONTENT_EXPORT AppCacheStorageReference
    : public base::RefCounted<AppCacheStorageReference> {
public:
    AppCacheStorage* storage() const { return storage_.get(); }

private:
    friend class AppCacheServiceImpl;
    friend class base::RefCounted<AppCacheStorageReference>;
    AppCacheStorageReference(std::unique_ptr<AppCacheStorage> storage);
    ~AppCacheStorageReference();

    std::unique_ptr<AppCacheStorage> storage_;
};

// Class that manages the application cache service. Sends notifications
// to many frontends.  One instance per user-profile. Each instance has
// exclusive access to its cache_directory on disk.
class CONTENT_EXPORT AppCacheServiceImpl
    : public AppCacheService {
public:
    class CONTENT_EXPORT Observer {
    public:
        // Called just prior to the instance being deleted.
        virtual void OnServiceDestructionImminent(AppCacheServiceImpl* service) { }

        // An observer method to inform consumers of reinitialzation. Managing
        // the lifetime of the old storage instance is a delicate process.
        // Consumers can keep the old disabled instance alive by hanging on to the
        // ref provided.
        virtual void OnServiceReinitialized(
            AppCacheStorageReference* old_storage_ref) { }
        virtual ~Observer() { }
    };

    // If not using quota management, the proxy may be NULL.
    explicit AppCacheServiceImpl(storage::QuotaManagerProxy* quota_manager_proxy);
    ~AppCacheServiceImpl() override;

    void Initialize(
        const base::FilePath& cache_directory,
        const scoped_refptr<base::SingleThreadTaskRunner>& db_thread,
        const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread);

    void AddObserver(Observer* observer)
    {
        observers_.AddObserver(observer);
    }

    void RemoveObserver(Observer* observer)
    {
        observers_.RemoveObserver(observer);
    }

    // For use in catastrophic failure modes to reboot the appcache system
    // without relaunching the browser.
    void ScheduleReinitialize();

    // AppCacheService implementation:
    void GetAllAppCacheInfo(AppCacheInfoCollection* collection,
        const net::CompletionCallback& callback) override;
    void DeleteAppCacheGroup(const GURL& manifest_url,
        const net::CompletionCallback& callback) override;

    // Deletes all appcaches for the origin, 'callback' is invoked upon
    // completion. This method always completes asynchronously.
    // (virtual for unit testing)
    virtual void DeleteAppCachesForOrigin(
        const GURL& origin, const net::CompletionCallback& callback);

    // Checks the integrity of 'response_id' by reading the headers and data.
    // If it cannot be read, the cache group for 'manifest_url' is deleted.
    void CheckAppCacheResponse(const GURL& manifest_url,
        int64_t cache_id,
        int64_t response_id);

    // Context for use during cache updates, should only be accessed
    // on the IO thread. We do NOT add a reference to the request context,
    // it is the callers responsibility to ensure that the pointer
    // remains valid while set.
    net::URLRequestContext* request_context() const { return request_context_; }
    void set_request_context(net::URLRequestContext* context)
    {
        request_context_ = context;
    }

    // The appcache policy, may be null, in which case access is always allowed.
    // The service does NOT assume ownership of the policy, it is the callers
    // responsibility to ensure that the pointer remains valid while set.
    AppCachePolicy* appcache_policy() const { return appcache_policy_; }
    void set_appcache_policy(AppCachePolicy* policy)
    {
        appcache_policy_ = policy;
    }

    // The factory may be null, in which case invocations of exe handlers
    // will result in an error response.
    // The service does NOT assume ownership of the factory, it is the callers
    // responsibility to ensure that the pointer remains valid while set.
    AppCacheExecutableHandlerFactory* handler_factory() const
    {
        return handler_factory_;
    }
    void set_handler_factory(
        AppCacheExecutableHandlerFactory* factory)
    {
        handler_factory_ = factory;
    }

    storage::SpecialStoragePolicy* special_storage_policy() const
    {
        return special_storage_policy_.get();
    }
    void set_special_storage_policy(storage::SpecialStoragePolicy* policy);

    storage::QuotaManagerProxy* quota_manager_proxy() const
    {
        return quota_manager_proxy_.get();
    }

    AppCacheQuotaClient* quota_client() const
    {
        return quota_client_;
    }

    // Each child process in chrome uses a distinct backend instance.
    // See chrome/browser/AppCacheDispatcherHost.
    void RegisterBackend(AppCacheBackendImpl* backend_impl);
    void UnregisterBackend(AppCacheBackendImpl* backend_impl);
    AppCacheBackendImpl* GetBackend(int id) const
    {
        BackendMap::const_iterator it = backends_.find(id);
        return (it != backends_.end()) ? it->second : NULL;
    }

    AppCacheStorage* storage() const { return storage_.get(); }

    base::WeakPtr<AppCacheServiceImpl> AsWeakPtr()
    {
        return weak_factory_.GetWeakPtr();
    }

    // Disables the exit-time deletion of session-only data.
    void set_force_keep_session_state() { force_keep_session_state_ = true; }
    bool force_keep_session_state() const { return force_keep_session_state_; }

protected:
    friend class content::AppCacheServiceImplTest;
    friend class content::AppCacheStorageImplTest;
    FRIEND_TEST_ALL_PREFIXES(content::AppCacheServiceImplTest,
        ScheduleReinitialize);

    class AsyncHelper;
    class DeleteHelper;
    class DeleteOriginHelper;
    class GetInfoHelper;
    class CheckResponseHelper;

    using PendingAsyncHelpers = std::map<AsyncHelper*, std::unique_ptr<AsyncHelper>>;
    using BackendMap = std::map<int, AppCacheBackendImpl*>;

    void Reinitialize();

    base::FilePath cache_directory_;
    scoped_refptr<base::SingleThreadTaskRunner> db_thread_;
    scoped_refptr<base::SingleThreadTaskRunner> cache_thread_;
    AppCachePolicy* appcache_policy_;
    AppCacheQuotaClient* quota_client_;
    AppCacheExecutableHandlerFactory* handler_factory_;
    std::unique_ptr<AppCacheStorage> storage_;
    scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
    scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
    PendingAsyncHelpers pending_helpers_;
    BackendMap backends_; // One 'backend' per child process.
    // Context for use during cache updates.
    net::URLRequestContext* request_context_;
    // If true, nothing (not even session-only data) should be deleted on exit.
    bool force_keep_session_state_;
    base::Time last_reinit_time_;
    base::TimeDelta next_reinit_delay_;
    base::OneShotTimer reinit_timer_;
    base::ObserverList<Observer> observers_;

private:
    base::WeakPtrFactory<AppCacheServiceImpl> weak_factory_;

    DISALLOW_COPY_AND_ASSIGN(AppCacheServiceImpl);
};

} // namespace content

#endif // CONTENT_BROWSER_APPCACHE_APPCACHE_SERVICE_IMPL_H_
